unit AVGrid;

interface
uses SysUtils, Classes, Vcl.Grids, System.Types, AVTypes, Generics.Collections,
     AVData, Math, Vcl.Graphics, AVCustomGrid, Windows, AVLibRes, Vcl.Controls,
     Messages, AVProtectedAccess, Vcl.Themes, Vcl.GraphUtil;

const
  GridGroupPannelHeight = 20;

type

  TAVGrid = class;
  TAVGridColumn = class;

  TAVColumnChangeType = (cctUpdate, cctUpdateHeader, cctUpdateContent);

  TOnGetImageIndex = procedure(Sender: TAVGridColumn; ADataRow: integer; var ImgIndex: integer) of object;
  TOnGetImageCount = procedure(Sender: TAVGridColumn; ADataRow: integer; var ImgCount: integer) of object;

  TAVGridColumn = class(TCollectionItem)
  private
    fName: string;
    fCaption: string;
    fCaptionAlignment: TAlignment;
    fAlignment: TAlignment;
    fVisible: boolean;
    fFont: TFont;
    fCaptionFont: TFont;
    fDataColumn: TAVBaseColumn;
    fValueType: TAVType;
    fColor: TColor;
    fFormat: string;
    fDrawImage: boolean;
    fDrawText: boolean;
    fImageIndex: integer;
    fOnGetImageIndex: TOnGetImageIndex;
    fImageCount: integer;
    fOnGetImageCount: TOnGetImageCount;
    fReadOnly: boolean;
    procedure HeaderChanged;
    procedure ContentChanged;
    procedure SetName(value: string);
    procedure SetCaption(value: string);
    procedure SetAlignment(value: TAlignment);
    procedure SetCaptionAlignment(value: TAlignment);
    procedure SetVisible(value: boolean);
    procedure SetFont(value: TFont);
    procedure SetColor(value: TColor);
    procedure SetCaptionFont(value: TFont);
    procedure SetValueType(value: TAVType);
    procedure SetFormat(value: string);
    procedure SetDrawImage(value: boolean);
    procedure SetDrawText(value: boolean);
    procedure SetImageIndex(value: integer);
    procedure SetImageCount(value: integer);
  protected
    function GetDisplayName: string; override;
    procedure SetCollection(Value: TCollection); override;
    procedure SetDisplayName(const Value: string); override;
    procedure Changed;
    procedure SetDefaults; virtual;
    function DataIsEmptry(rowIndex: integer): boolean;
    function FormatValue(value: TAVValue): string;
    function GetImageCount(DataRow: integer): integer;

    property DataColumn: TAVBaseColumn read fDataColumn write fDataColumn;
  public
    Properties: TAVProperties;
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    function GetText(rowIndex: integer): string;
    procedure SetText(rowIndex: integer; value: string);
  published
    property Name: string read fName write SetName;
    property Caption: string read fCaption write SetCaption;
    property CaptionAlignment: TAlignment read fCaptionAlignment write SetCaptionAlignment;
    property Alignment: TAlignment read fAlignment write SetAlignment;
    property Visible: boolean read fVisible write SetVisible;
    property Font: TFont read fFont write SetFont;
    property CaptionFont: TFont read fCaptionFont write SetCaptionFont;
    property ValueType: TAVType read fValueType write SetValueType;
    property Color: TColor read fColor write SetColor;
    property Format: string read fFormat write SetFormat;
    property DrawImage: boolean read fDrawImage write SetDrawImage;
    property DrawText: boolean read fDrawText write SetDrawText;
    property ImageIndex: integer read fImageIndex write SetImageIndex;
    property ImagesCount: integer read fImageCount write SetImageCount;
    property ReadOnly: boolean read fReadOnly write fReadOnly;

    property OnGetImageIndex: TOnGetImageIndex read fOnGetImageIndex write fOnGetImageIndex;
    property OnGetImageCount: TOnGetImageCount read fOnGetImageCount write fOnGetImageCount;
  end;

  TAVGridColumns = class(TCollection)
  private
    fOwner: TAVGrid;
    fVisibleColumns: TList<TAVGridColumn>;
    function GetColByName(colName: string): TAVGridColumn;
    function GetNewName(forCol: TAVGridColumn): string;
    function GetItem(Index: Integer): TAVGridColumn;
    procedure SetItem(Index: Integer; Value: TAVGridColumn);

    procedure ColumnAdded(column: TAVGridColumn);
    procedure ColumnRemoved(column: TAVGridColumn);
    procedure ColumnChanged(column: TAVGridColumn; changeType: TAVColumnChangeType = cctUpdate);
    function ColumnVisibleIndex(column: TAVGridColumn): integer;
  protected
    function GetOwner : TPersistent; override;
    procedure Update(Item: TCollectionItem); override;
  public
    constructor Create(AOwner: TAVGrid);
    destructor Destroy; override;
    procedure MoveVisible(FromIndex, ToIndex: integer);

    property Owner: TAVGrid read fOwner;
    property Items[index: integer]: TAVGridColumn read GetItem write SetItem; default;
    property VisibleColumns: TList<TAVGridColumn> read fVisibleColumns;
  end;

  TAVCellType = (ctData, ctHeader, ctStatus, ctCorner);
  TOnValidate = procedure(Sender: TAVGrid; DataColumn: TAVBaseColumn; DataRow: integer) of object;
  TAVGridState = (gstNormal, gstInNewRow);
  TOnSort = procedure(Sender: TAVGrid; SortColumns: TArray<TAVBaseColumn>) of object;
  TOnDrawValue = procedure(Sender: TAVGrid; ACol, ARow: integer; Column: TAVGridColumn;
       ARect: TRect; State: TGridDrawState; var Handled: boolean) of object;
  TOnCanEdit = function(Sender: TAVGrid; Column: TAVGridColumn; DataRow: integer): boolean of object;

  TImagesInfo = record
    ImageCount: integer;
    ImageWidth: integer;
    Width: integer;
    ImagesRect: TRect;
    IsResImage: boolean;
    function ImageRect(ImageIndex: integer): TRect;
  end;

  TMouseTrackInfo = class(TObject)
  private
    fGrid: TAVGrid;
    fCurrCell: TGridCoord;
    fCurrCellRect: TRect;
    fCurrImage: integer;
    fCurrImageRect: TRect;
    fMouseDownCell: TGridCoord;
    fMouseDownImage: integer;
    procedure SetCurrCell(value: TGridCoord);
    function InCell: boolean; inline;
    procedure DoCurrCellLeave;
    procedure DoCurrCellEnter;
    procedure SetCurrImage(imIndex: integer; imRect: TRect);
  public
    constructor Create(AGrid: TAVGrid);
    destructor Destroy; override;

    procedure TrackInCell(X, Y: integer);
    procedure DoMouseDown(X, Y: integer);
    procedure DoMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    function MouseUnderCell(ACol, ARow: integer): boolean;
    function MouseUnderImage(imIndex: integer): boolean;

    property CurrCell: TGridCoord read fCurrCell write SetCurrCell;
  end;

  TAVGridGroupPannel = class(TCustomControl)
  protected
    procedure Paint; override;
    function GetGrid: TAVGrid;
  public
    constructor Create(AOwner: TAVGrid);
    destructor Destroy; override;

    property Grid: TAVGrid read GetGrid;
  end;

  TDrawColors = record
    BaseBackColor: TColor;
    BackColor: TColor;
    SelColor: TColor;
    CurrColor: TColor;
    TextColor: TColor;
  end;

  TAVGrid = class(TAVCustomGrid)
  private
    fData: TAVData;
    fColumns: TAVGridColumns;
    fShowStatusCol: boolean;
    fOnValidate: TOnValidate;
    fGridState: TAVGridState;
    fAcceptAddRow: boolean;
    fSelectionColor: TColor;
    fOnBeforeSort: TOnSort;
    fOnAfterSort: TOnSort;
    fImageList: TImageList;
    fOnDrawValue: TOnDrawValue;
    fMouseTrackInfo: TMouseTrackInfo;
    fOnCanEdit: TOnCanEdit;
    fGroupPannel: TAVGridGroupPannel;
    fEnableGrouping: boolean;
    fAlwaysShowGroupPannel: boolean;
    fUseThemeForCell: boolean;
    procedure SetDefaults;
    procedure SetColumns(value: TAVGridColumns);
    function StatusColNum: integer;
    function GetReadOnly: boolean;
    procedure SetReadOnly(value: boolean);
    function GetColumn(ACol: integer): TAVGridColumn;
    function GetDataRow(ARow: integer): integer;
    function GetRowFromDataRow(dataRow: integer): integer;
    function DataRowIsEmpty(dataRow: integer): boolean;
    function ValidateNewRow: boolean;
    function ExitFromInNewRow: boolean;
    procedure RemoveDataRow(dataRow: integer);
    procedure SetSelectionColor(value: TColor);
    procedure SetEnableGrouping(value: boolean);
    procedure SetAlwaysShowGroupPannel(value: boolean);
  protected
    ImagesInfo: TImagesInfo;
    DrawColors: TDrawColors;
    procedure DrawText(ARect: TRect; Text: string; al: TAlignment; ImageWidth: integer); overload;
    procedure DrawText(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState; CellType: TAVCellType); overload;
    procedure DrawValue(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState; Column: TAVGridColumn);
    procedure DrawResImage(ARow: Longint; ImageIndex: integer; ImageRect: TRect; Column: TAVGridColumn; UnderMouse: boolean);
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); override;
    procedure DrawSelectionEdge(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
    procedure DrawCellBackground(const ARect: TRect; AColor: TColor; AState: TGridDrawState; ACol, ARow: Integer); override;
    procedure DrawCellHighlight(const ARect: TRect; AState: TGridDrawState; ACol, ARow: Integer); override;
    procedure DrawBackground(const ARect: TRect; AState: TGridDrawState; ACol, ARow: Integer);
    procedure CalcDrawColors(ACol, ARow: Longint; AState: TGridDrawState);
    procedure VisibleColumnsChanged;
    function GetEditText(ACol, ARow: Longint): string; override;
    procedure SetEditText(ACol, ARow: Longint; const Value: string); override;
    procedure PrepareDataRowCount(rowIndex: integer);
    procedure DoValidate(DataColumn: TAVBaseColumn; DataRow: integer);
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure ProcessEditKey(Column: TAVGridColumn; Key: Word; Shift: TShiftState);
    procedure CellSelected(ACol, ARow: Longint); override;
    procedure SelectionMoved(const OldSel: TGridRect); override;
    procedure ColumnMoved(FromIndex, ToIndex: Longint); override;
    procedure FixedCellClick(ACol, ARow: Longint); override;
    procedure Sort(SortColumns: TArray<TAVBaseColumn>);
    function GetCellRect(ACol, ARow: integer): TRect;
    procedure CalcImageInfo(ACol, ARow: integer; Column: TAVGridColumn);
    function GetResImageCount(ARow: integer; Column: TAVGridColumn): integer;
    procedure DoMouseTrack(Shift: TShiftState; X, Y: Integer; DrawInfo: TGridDrawInfo); override;
    function CanEdit(column: TAVGridColumn; DataRow: integer): boolean;
    function CanEditShow: Boolean; override;
    procedure DoImageClick(ACol, ARow: integer; ImageIndex: integer);
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure WmNCCalcSize(var Msg: TWMNCCalcSize); message WM_NCCALCSIZE;
    procedure CallUpdateScroll;
    procedure UpdateGridWindow;
    function GroupPannelShown: boolean;
    function GetBorderWidth: integer;
    procedure SetGroupPannelPos;
    procedure ShowGroupPannel(show: boolean);
    procedure Resize; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;
    procedure UpdateRowCount;

    property Data: TAVData read fData;
  published
    property Columns: TAVGridColumns read fColumns write SetColumns;
    property ShowStatusCol: boolean read fShowStatusCol write fShowStatusCol;
    property ReadOnly: boolean read GetReadOnly write SetReadOnly;
    property AcceptAddRow: boolean read fAcceptAddRow write fAcceptAddRow;
    property SelectionColor: TColor read fSelectionColor write SetSelectionColor;
    property ImageList: TImageList read fImageList write fImageList;
    property EnableGrouping: boolean read fEnableGrouping write SetEnableGrouping;
    property AlwaysShowGroupPannel: boolean read fAlwaysShowGroupPannel write SetAlwaysShowGroupPannel;

    property OnValidate: TOnValidate read fOnValidate write fOnValidate;
    property OnBeforeSort: TOnSort read fOnBeforeSort write fOnBeforeSort;
    property OnAfterSort: TOnSort read fOnAfterSort write fOnAfterSort;
    property OnDrawValue: TOnDrawValue read fOnDrawValue write fOnDrawValue;
    property OnCanEdit: TOnCanEdit read fOnCanEdit write fOnCanEdit;
    property UseThemeForCell: boolean read fUseThemeForCell write fUseThemeForCell;
    property DrawingStyle;

    property Align;
    property Anchors;
  end;

  procedure Register;

implementation
uses AVUtils;

  procedure Register;
  begin
    Classes.RegisterComponents('AVLib', [TAVGrid]);
  end;

//TAVGridColumn

    constructor TAVGridColumn.Create(Collection: TCollection);
    begin
      if Collection <> nil then fName := TAVGridColumns(Collection).GetNewName(Self);
      fCaption := fName;
      fFont := TFont.Create;
      fCaptionFont := TFont.Create;
      fVisible := true;
      inherited Create(Collection);
      SetDefaults;
    end;

    destructor TAVGridColumn.Destroy;
    begin
      fFont.Free;
      fCaptionFont.Free;
      inherited Destroy;
    end;

    function TAVGridColumn.DataIsEmptry(rowIndex: integer): boolean;
    begin
      if (fDataColumn <> nil) and (rowIndex < fDataColumn.Rows.DisplayRowCount)
        then Result := fDataColumn.DisplayItems[rowIndex].IsEmpty
        else Result := true;
    end;

    function TAVGridColumn.FormatValue(value: TAVValue): string;
    begin
      if Trim(fFormat) = '' then Result := value.AsString else begin
        Result := value.FormatedValue[fFormat];
      end;
    end;

    function TAVGridColumn.GetImageCount(DataRow: integer): integer;
    begin
      if Assigned(fOnGetImageCount)
      then fOnGetImageCount(Self, DataRow, Result)
      else Result := fImageCount;
    end;

    function TAVGridColumn.GetText(rowIndex: integer): string;
    begin
      if (fDataColumn <> nil) and (rowIndex < fDataColumn.Rows.DisplayRowCount) then begin
        Result := FormatValue(fDataColumn.DisplayItems[rowIndex]);
      end else begin
        Result := '';
      end;
    end;

    procedure TAVGridColumn.SetText(rowIndex: integer; value: string);
    begin
      if (fDataColumn <> nil) and (rowIndex < fDataColumn.Rows.DisplayRowCount) then begin
        if fFormat <> ''
            then fDataColumn.NewValue.FormatedValue[fFormat] := value
            else fDataColumn.NewValue.AsString := value;
        if fDataColumn.AplyNewDisplayValue(rowIndex) then begin
          TAVGridColumns(Collection).Owner.DoValidate(fDataColumn, rowIndex);
        end;
      end;
    end;

    procedure TAVGridColumn.SetDefaults;
    begin
      fFont.Name := 'Verdana';
      fFont.Size := 8;
      fCaptionAlignment := taCenter;
      fColor := clWindow;
      fImageIndex := -1;
      fDrawText := true;
    end;

    procedure TAVGridColumn.SetName(value: string);
    var col: TAVGridColumn;
    begin
      if fName <> value then begin
        if (Collection <> nil) then begin
          col := TAVGridColumns(Collection).GetColByName(value);
          if (col <> nil) and (col <> self) then raise Exception.Create('Dublicate name');
        end;
        fName := value;
        Changed;
      end;
    end;

    procedure TAVGridColumn.HeaderChanged;
    begin
      TAVGridColumns(Collection).ColumnChanged(self, cctUpdateHeader);
    end;

    procedure TAVGridColumn.ContentChanged;
    begin
      TAVGridColumns(Collection).ColumnChanged(self, cctUpdateContent);
    end;

    procedure TAVGridColumn.SetCaption(value: string);
    begin
      if fCaption <> value then begin
        fCaption := value;
        HeaderChanged;
      end;
    end;

    procedure TAVGridColumn.SetAlignment(value: TAlignment);
    begin
      if fAlignment <> value then begin
        fAlignment := value;
        ContentChanged;
      end;
    end;

    procedure TAVGridColumn.SetCaptionAlignment(value: TAlignment);
    begin
      if fCaptionAlignment <> value then begin
        fCaptionAlignment := value;
        HeaderChanged;
      end;
    end;

    procedure TAVGridColumn.SetVisible(value: boolean);
    begin
      if fVisible <> value then begin
        fVisible := value;
        TAVGridColumns(Collection).ColumnChanged(self);
      end;
    end;

    procedure TAVGridColumn.SetFont(value: TFont);
    begin
      if value <> nil then fFont.Assign(value);
    end;

    procedure TAVGridColumn.SetColor(value: TColor);
    begin
      if fColor <> value then begin
        fColor := value;
        TAVGridColumns(Collection).ColumnChanged(self, cctUpdateContent);
      end;
    end;

    procedure TAVGridColumn.SetCaptionFont(value: TFont);
    begin
      if value <> nil then begin
        fCaptionFont.Assign(value);
        HeaderChanged;
      end;
    end;

    procedure TAVGridColumn.SetValueType(value: TAVType);
    begin
      if fValueType <> value then begin
        fValueType := value;
        TAVGridColumns(Collection).ColumnChanged(self, cctUpdate);
      end;
    end;

    procedure TAVGridColumn.SetFormat(value: string);
    begin
      if fFormat <> value then begin
        fFormat := value;
        TAVGridColumns(Collection).ColumnChanged(self, cctUpdateContent);
      end;
    end;

    procedure TAVGridColumn.SetDrawImage(value: boolean);
    begin
      if fDrawImage <> value then begin
        fDrawImage := value;
        TAVGridColumns(Collection).ColumnChanged(self, cctUpdateContent);
      end;
    end;

    procedure TAVGridColumn.SetDrawText(value: boolean);
    begin
      if fDrawText <> value then begin
        fDrawText := value;
        TAVGridColumns(Collection).ColumnChanged(self, cctUpdateContent);
      end;
    end;

    procedure TAVGridColumn.SetImageIndex(value: integer);
    begin
      if fImageIndex <> value then begin
        fImageIndex := value;
        TAVGridColumns(Collection).ColumnChanged(self, cctUpdateContent);
      end;
    end;

    procedure TAVGridColumn.SetImageCount(value: integer);
    begin
      if fImageCount <> value then begin
        fImageCount := Math.Max(value, 0);
        TAVGridColumns(Collection).ColumnChanged(self, cctUpdateContent);
      end;
    end;

    function TAVGridColumn.GetDisplayName: string;
    begin
      Result := Name;
    end;

    procedure TAVGridColumn.SetDisplayName(const Value: string);
    begin
      Name := Value;
    end;

    procedure TAVGridColumn.SetCollection(Value: TCollection);
    var oldCollection: TCollection;
    begin
      if Collection <> Value then
      begin
        oldCollection := Collection;
        inherited SetCollection(Value);
        if (oldCollection <> nil) and (TAVGridColumns(oldCollection).Owner <> nil)
          then TAVGridColumns(oldCollection).ColumnRemoved(Self);
        if (Collection <> nil) and (TAVGridColumns(Collection).Owner <> nil)
          then TAVGridColumns(Collection).ColumnAdded(Self);
      end;
    end;

    procedure TAVGridColumn.Changed;
    begin
      if (Collection <> nil)
          then TAVGridColumns(Collection).ColumnChanged(Self);
    end;

//TAVGridColumns

    constructor TAVGridColumns.Create(AOwner: TAVGrid);
    begin
      inherited Create(TAVGridColumn);
      fOwner := AOwner;
      fVisibleColumns := TList<TAVGridColumn>.Create;
    end;

    destructor TAVGridColumns.Destroy;
    begin
      fVisibleColumns.Free;
      inherited Destroy;
    end;

    procedure TAVGridColumns.MoveVisible(FromIndex, ToIndex: integer);
    begin
      if (FromIndex >= 0) and (FromIndex < fVisibleColumns.Count) and
         (ToIndex >= 0) and (ToIndex < fVisibleColumns.Count) then
      fVisibleColumns.Move(FromIndex, ToIndex);
    end;

    procedure TAVGridColumns.ColumnAdded(column: TAVGridColumn);
    var dataCol: TAVBaseColumn;
    begin
      dataCol := fOwner.Data.Columns.ByName[column.Name];
      if dataCol = nil then begin
        dataCol := fOwner.Data.AddCol(column.Name, column.ValueType);
        dataCol.Properties['#RuntimeCreated'].AsBool := true;
      end;
      column.DataColumn := dataCol;
    end;

    procedure TAVGridColumns.ColumnRemoved(column: TAVGridColumn);
    var dataCol: TAVBaseColumn;
    begin
      dataCol := column.DataColumn;
      column.DataColumn := nil;
      if (dataCol <> nil) and (fOwner.Data.Columns.IndexOf(dataCol) >= 0) then begin
        if dataCol.Properties['#RuntimeCreated'].AsBool
          then fOwner.Data.Columns.Delete(fOwner.Data.Columns.IndexOf(dataCol));
      end;
    end;

    procedure TAVGridColumns.ColumnChanged(column: TAVGridColumn; changeType: TAVColumnChangeType = cctUpdate);
    var dataCol: TAVBaseColumn;
        n: integer;
    begin
      case changeType of
        cctUpdate: begin
          dataCol := column.DataColumn;
          if (dataCol <> nil) then begin
            if column.ValueType <> dataCol.ValueType then begin
              column.DataColumn := fOwner.Data.Columns.NewColumnType(dataCol, column.ValueType);
            end else
            if column.Name <> dataCol.Name then begin
              dataCol.Name := column.Name;
            end;
          end;
          Update(column);
        end;
        cctUpdateHeader: begin
          n := ColumnVisibleIndex(column);
          if n >= 0 then fOwner.InvalidateCell(n + fOwner.StatusColNum, 0);
          end;
      end;
    end;

    function TAVGridColumns.ColumnVisibleIndex(column: TAVGridColumn): integer;
    begin
      Result := fVisibleColumns.IndexOf(column);
    end;

    function TAVGridColumns.GetColByName(colName: string): TAVGridColumn;
    var i: integer;
    begin
      Result := nil;
      for i := 0 to Count - 1 do
        if CompareText(colName, Items[i].Name) = 0 then Exit(Items[i]);
    end;

    function TAVGridColumns.GetNewName(forCol: TAVGridColumn): string;
    var i: integer;
        col: TAVGridColumn;
    begin
      i := 0;
      repeat
        Inc(i);
        Result := 'Col' + IntToStr(i);
        col := GetColByName(Result);
      until (col = nil) or (col = forCol);
    end;

    function TAVGridColumns.GetOwner : TPersistent;
    begin
      Result := fOwner;
    end;

    procedure TAVGridColumns.Update(Item: TCollectionItem);
    var i: integer;
    begin
      fVisibleColumns.Clear;
      for i := 0 to Count - 1 do
        if Items[i].Visible then fVisibleColumns.Add(Items[i]);
      fOwner.VisibleColumnsChanged;
    end;

    function TAVGridColumns.GetItem(Index: Integer): TAVGridColumn;
    begin
      Result := TAVGridColumn(inherited GetItem(Index));
    end;

    procedure TAVGridColumns.SetItem(Index: Integer; Value: TAVGridColumn);
    begin
      inherited SetItem(Index, Value);
    end;

//TImagesInfo

    function TImagesInfo.ImageRect(ImageIndex: integer): TRect;
    begin
      Result := ImagesRect;
      Result.Left := Result.Left + ImageWidth * ImageIndex;
    end;

//TMouseTrackInfo

    constructor TMouseTrackInfo.Create(AGrid: TAVGrid);
    begin
      inherited Create;
      fGrid := AGrid;
    end;

    destructor TMouseTrackInfo.Destroy;
    begin
      inherited Destroy;
    end;

    procedure TMouseTrackInfo.SetCurrCell(value: TGridCoord);
    begin
      if (fCurrCell.X <> value.X) or (fCurrCell.Y <> value.Y) then begin
        DoCurrCellLeave;
        fCurrCell := value;
        DoCurrCellEnter;
      end;
    end;

    function TMouseTrackInfo.InCell: boolean;
    begin
      Result := (fCurrCell.X >= 0) and (fCurrCell.Y > 0);
    end;

    procedure TMouseTrackInfo.DoCurrCellLeave;
    begin
      if not InCell then exit;
      Self.SetCurrImage(-1, TRect.Empty);
      fGrid.InvalidateCell(fCurrCell.X, fCurrCell.Y);
    end;

    procedure TMouseTrackInfo.DoCurrCellEnter;
    begin
      if not InCell then exit;
      fGrid.InvalidateCell(fCurrCell.X, fCurrCell.Y);
      fCurrCellRect := fGrid.GetCellRect(fCurrCell.X, fCurrCell.Y);
    end;

    procedure TMouseTrackInfo.SetCurrImage(imIndex: integer; imRect: TRect);
    var needInvalidate: boolean;
    begin
      if (fCurrImage <> imIndex) or (fCurrImageRect <> imRect) then begin
        needInvalidate := false;
        if (fCurrImage >= 0) and InCell then begin
          needInvalidate := true;
        end;
        fCurrImage := imIndex;
        fCurrImageRect := imRect;
        if (fCurrImage >= 0) and InCell then begin
          needInvalidate := true;
        end;

        if needInvalidate then fGrid.InvalidateCell(fCurrCell.X, fCurrCell.Y);
      end;
    end;

    procedure TMouseTrackInfo.TrackInCell(X, Y: integer);
    var i, foundIndex: integer;
        iRect: TRect;
    begin
      if not InCell then exit;

      fGrid.CalcImageInfo(fCurrCell.X, fCurrCell.Y, nil);
      foundIndex := -1;
      for i := 0 to fGrid.ImagesInfo.ImageCount do begin
        iRect := fGrid.ImagesInfo.ImageRect(i);
        if (X >= iRect.Left) and (X <= iRect.Right) and (Y >= iRect.Top) and (Y <= iRect.Bottom) then
        begin
          foundIndex := i;
          break;
        end;
      end;
      if foundIndex < 0 then iRect := TRect.Empty;
      SetCurrImage(foundIndex, iRect);
    end;

    procedure TMouseTrackInfo.DoMouseDown(X, Y: integer);
    begin
      if InCell then begin
        TrackInCell(X, Y);
        fMouseDownCell := fCurrCell;
        fMouseDownImage := fCurrImage;
      end else begin
        fMouseDownCell.X := -1;
        fMouseDownCell.Y := -1;
        fMouseDownImage := -1;
      end;
    end;

    procedure TMouseTrackInfo.DoMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    begin
      if InCell then begin
        TrackInCell(X, Y);
        if (Button = mbLeft) and
           (fMouseDownCell.X = fCurrCell.X) and (fMouseDownCell.Y = fCurrCell.Y) and
           (fMouseDownImage = fCurrImage) and (fCurrImage >= 0)
        then begin
          fGrid.DoImageClick(fCurrCell.X, fCurrCell.Y, fCurrImage);
        end;
      end;
    end;

    function TMouseTrackInfo.MouseUnderCell(ACol, ARow: integer): boolean;
    begin
      Result := (fCurrCell.X = ACol) and (fCurrCell.Y = ARow);
    end;

    function TMouseTrackInfo.MouseUnderImage(imIndex: integer): boolean;
    begin
      Result := (fCurrImage = imIndex);
    end;

//TAVGridGroupPannel

    constructor TAVGridGroupPannel.Create(AOwner: TAVGrid);
    begin
      inherited Create(AOwner);
    end;

    destructor TAVGridGroupPannel.Destroy;
    begin
      inherited Destroy;
    end;

    procedure TAVGridGroupPannel.Paint;
    var
      LStyle: TCustomStyleServices;
      LDetails: TThemedElementDetails;
      ARect, R: TRect;
      StartColor, EndColor, LColor: TColor;
      SaveIndex: Integer;
    begin
      ARect := ClientRect;
      LStyle := StyleServices;

      if (Grid.DrawingStyle = gdsThemed) and LStyle.Enabled then
        begin
          R := ARect;
          Inc(R.Bottom);
          Inc(R.Right);
          LDetails := LStyle.GetElementDetails(tgFixedCellNormal);
          SaveIndex := SaveDC(Canvas.Handle);
          try
            LStyle.DrawElement(Canvas.Handle, LDetails, R, ARect);
          finally
            RestoreDC(Canvas.Handle, SaveIndex);
          end;
      end else
      begin
        if LStyle.Enabled then
        begin
          LDetails := LStyle.GetElementDetails(tgGradientFixedCellNormal);
          if LStyle.GetElementColor(LDetails, ecGradientColor1, LColor) and (LColor <> clNone)
            then StartColor := LColor
            else StartColor := Grid.GradientStartColor;
          if LStyle.GetElementColor(LDetails, ecGradientColor2, LColor) and (LColor <> clNone)
            then EndColor := LColor
            else EndColor := Grid.GradientEndColor;
          if LStyle.GetElementColor(LDetails, ecTextColor, LColor) and (LColor <> clNone)
            then Canvas.Font.Color := LColor
            else Canvas.Font.Color := Grid.Font.Color;
        end else
        begin
          StartColor := Grid.GradientStartColor;
          EndColor := Grid.GradientEndColor;
        end;
        GradientFillCanvas(Canvas, StartColor, EndColor, ARect, gdVertical);
      end;

      Canvas.Brush.Style := bsClear;
    end;

    function TAVGridGroupPannel.GetGrid: TAVGrid;
    begin
      Result := Owner as TAVGrid;
    end;

//TAVGrid

    constructor TAVGrid.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      fData := TAVData.Create;
      fColumns := TAVGridColumns.Create(Self);
      fMouseTrackInfo := TMouseTrackInfo.Create(Self);
      SetDefaults;
    end;

    destructor TAVGrid.Destroy;
    begin
      fColumns.Free;
      fData.Free;
      fMouseTrackInfo.Free;
      inherited Destroy;
    end;

    procedure TAVGrid.Loaded;
    begin
      inherited;
      UpdateRowCount;
    end;

    procedure TAVGrid.UpdateRowCount;
    begin
      RowCount := Math.Max(1, fData.DisplayRowCount) + 1;
      Invalidate;
    end;

    procedure TAVGrid.SetColumns(value: TAVGridColumns);
    begin
      fColumns.Assign(value);
    end;

    function TAVGrid.StatusColNum: integer;
    begin
      if fShowStatusCol
        then Result := 1
        else Result := 0;
    end;

    function TAVGrid.GetReadOnly: boolean;
    begin
      Result := not (goEditing in Options);
    end;

    procedure TAVGrid.SetReadOnly(value: boolean);
    begin
      if value
        then Options := Options - [goEditing]
        else Options := Options + [goEditing];
    end;

    procedure TAVGrid.SetDefaults;
    begin
      FSaveCellExtents := false; //do not save col width and row height in dfm

      Options := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
        goRangeSelect, goDrawFocusSelected, goColSizing, goColMoving, goTabs,
        goThumbTracking, goFixedRowClick, goFixedHotTrack];

      fShowStatusCol := true;
      DefaultRowHeight := 16;
      fSelectionColor := 16765606;
      ColWidths[0] := 16;
    end;

    procedure TAVGrid.DrawText(ARect: TRect; Text: string; al: TAlignment; ImageWidth: integer);
    var x, w: integer;
    begin
      if ImageWidth > 0 then Inc(ImageWidth, 5);
      
      if al = taLeftJustify then x := 2 + ImageWidth else begin
        w := Canvas.TextWidth(Text) + ImageWidth;
        if al = taRightJustify
          then x := ARect.Width - w - 2
          else x := ((ARect.Width - w) div 2) + ImageWidth;
      end;

      Canvas.TextRect(ARect, ARect.Left + x, ARect.Top + 1, Text);
    end;

    procedure TAVGrid.DrawText(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState; CellType: TAVCellType);
    var s: string;
        col: TAVGridColumn;
        al: TAlignment;
    begin
      s := '';

      if CellType in [ctHeader, ctData]
      then col := GetColumn(ACol)
      else col := nil;

      case CellType of
        ctHeader: begin
          if col <> nil then begin
            s := col.Caption;
            Canvas.Font := col.CaptionFont;
            Canvas.Font.Color := DrawColors.TextColor;
            al := col.CaptionAlignment;
            if s <> '' then DrawText(ARect, s, al, 0);
          end;
        end;
        ctData: begin
          DrawValue(ACol, ARow, ARect, AState, col);
        end;
      end;
    end;

    procedure TAVGrid.DrawValue(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState; Column: TAVGridColumn);
    var rowIndex: integer;
        imageWidth: integer;
        handled: boolean;
        i: integer;
        underCell, underMouse: boolean;
    begin
      handled := false;
      if Assigned(fOnDrawValue) then fOnDrawValue(Self, ACol, ARow, Column, ARect, AState, handled);
      if handled then exit;      

      imageWidth := 0;

      if (Column <> nil) then begin
        if Column.DrawImage then begin
          CalcImageInfo(ACol, ARow, Column);
          imageWidth := ImagesInfo.Width;
        end;

        rowIndex := GetDataRow(ARow);
        if Column.DrawText and (rowIndex < fData.DisplayRowCount) then begin
          Canvas.Font := Column.Font;
          Canvas.Font.Color := DrawColors.TextColor;

          DrawText(ARect, Column.GetText(rowIndex), Column.Alignment, imageWidth);
        end;

        if Column.DrawImage then
        begin
          underCell := fMouseTrackInfo.MouseUnderCell(ACol, ARow);
          underMouse := false;
          for i := 0 to ImagesInfo.ImageCount - 1 do
          begin
            if underCell then underMouse := fMouseTrackInfo.MouseUnderImage(i);
            if ImagesInfo.IsResImage then begin
              DrawResImage(ARow, i,  ImagesInfo.ImageRect(i), Column, underMouse);
            end else begin

            end;
          end;
        end;

      end;
    end;

    procedure TAVGrid.DrawResImage(ARow: Longint; ImageIndex: integer; ImageRect: TRect; Column: TAVGridColumn; UnderMouse: boolean);
    var
      checked: boolean;
      dataRow: integer;
      Details: TThemedElementDetails;
      ThemedButton: TThemedButton;
      R: TRect;
      SaveIndex: integer;
    begin
      dataRow := GetDataRow(ARow);
      if Column.ValueType = avtBool then begin
        checked := false;
        if (Column.DataColumn <> nil) and (dataRow < fData.DisplayRowCount)
          then checked := Column.DataColumn.DisplayItems[dataRow].AsBool;

        if StyleServices.Available then
        begin
          if UnderMouse and not(ReadOnly or Column.ReadOnly) then begin
            if checked
              then ThemedButton := tbCheckBoxCheckedHot
              else ThemedButton := tbCheckBoxUncheckedHot;
          end else begin
            if checked
              then ThemedButton := tbCheckBoxCheckedNormal
              else ThemedButton := tbCheckBoxUncheckedNormal;
          end;
          Details := StyleServices.GetElementDetails(ThemedButton);
          R := ImageRect;
          SaveIndex := SaveDC(Canvas.Handle);
          try
            StyleServices.DrawElement(Canvas.Handle, Details, R);
          finally
            RestoreDC(Canvas.Handle, SaveIndex);
          end;
        end else
        begin
          if UnderMouse and not ReadOnly and not Column.ReadOnly
            then AVRes.Images.Draw(Canvas, ImageRect.Left, ImageRect.Top, Integer(GRID_CHECKBOX_MOUSE), true)
            else AVRes.Images.Draw(Canvas, ImageRect.Left, ImageRect.Top, Integer(GRID_CHECKBOX), true);
          if checked then AVRes.Images.Draw(Canvas, ImageRect.Left, ImageRect.Top, Integer(GRID_CHECK), true);
        end;
      end;
    end;

    function TAVGrid.GetCellRect(ACol, ARow: integer): TRect;
    begin
      Result := CellRect(ACol, ARow);
      Result.Width := ColWidths[ACol];
      Result.Height := RowHeights[ARow];
    end;

    procedure TAVGrid.CalcImageInfo(ACol, ARow: integer; Column: TAVGridColumn);
    var dataRow: integer;
        w: integer;
        rLeft, rTop: integer;
        rect: TRect;
    begin
      ImagesInfo.IsResImage := false;
      ImagesInfo.ImageCount := 0;
      ImagesInfo.ImageWidth := 0;
      ImagesInfo.Width := 0;
      ImagesInfo.ImagesRect := TRect.Empty;

      if Column = nil then Column := GetColumn(ACol);
      if Column = nil then exit;

      dataRow := GetDataRow(ARow);
      ImagesInfo.ImageCount := Column.GetImageCount(dataRow);
      if (ImagesInfo.ImageCount = 0) then
      begin
        ImagesInfo.ImageCount := GetResImageCount(ARow, Column);
        if ImagesInfo.ImageCount > 0 then ImagesInfo.IsResImage := true;
      end;
      if ImagesInfo.ImageCount = 0 then exit;

      if ImagesInfo.IsResImage then begin
        ImagesInfo.ImageWidth := AVRes.Images.Width;
      end else begin
        if Assigned(fImageList)
          then ImagesInfo.ImageWidth := fImageList.Width
          else begin ImagesInfo.ImageCount := 0; exit; end;
      end;

      ImagesInfo.Width := ImagesInfo.ImageWidth * ImagesInfo.ImageCount;

      rect := GetCellRect(ACol, ARow);
      w := ImagesInfo.Width;
      if Column.DrawText and (Column.Alignment <> taLeftJustify) then w := w + Canvas.TextWidth(Column.GetText(dataRow));
      case Column.Alignment of
        taLeftJustify: rLeft := 2;
        taRightJustify: rLeft := rect.Right - w - 2;
        taCenter: rLeft := rect.Left + (rect.Width - w) div 2;
      end;
      rect.Left := rLeft;
      rect.Width := ImagesInfo.Width;
      rTop := rect.Top + (rect.Height - ImagesInfo.Width) div 2;
      rect.Top := rTop;
      rect.Height := ImagesInfo.Width;
      ImagesInfo.ImagesRect := rect;
    end;

    function TAVGrid.GetResImageCount(ARow: integer; Column: TAVGridColumn): integer;
    begin
      Result := 0;
      if (Column.ValueType = avtBool) and (Column.ImageIndex = -1) then begin
        Result := 1;
      end;
    end;

    procedure TAVGrid.DoMouseTrack(Shift: TShiftState; X, Y: Integer; DrawInfo: TGridDrawInfo);
    begin
      inherited DoMouseTrack(Shift, X, Y, DrawInfo);
      fMouseTrackInfo.CurrCell := CellHit;
      fMouseTrackInfo.TrackInCell(X, Y);
    end;

    function TAVGrid.CanEdit(column: TAVGridColumn; DataRow: integer): boolean;
    begin
      if ReadOnly or column.ReadOnly then Exit(false);

      if Assigned(fOnCanEdit)
      then Result := fOnCanEdit(Self, column, DataRow)
      else Result := true;
    end;

    function TAVGrid.CanEditShow: Boolean;
    var column: TAVGridColumn;
    begin
      if not (inherited CanEditShow) then Exit(false);

      column := GetColumn(Col);
      if column = nil then Exit(false);

      Result := CanEdit(column, GetDataRow(Row));

      if Result and (column.ValueType = avtBool) and (column.DrawImage)
      then Result := false;
    end;

    procedure TAVGrid.DoImageClick(ACol, ARow: integer; ImageIndex: integer);
    var column: TAVGridColumn;
        dataRow: integer;
        v: boolean;
    begin
      column := GetColumn(ACol);
      if (column = nil) then exit;

      dataRow := GetDataRow(ARow);
      if (column.ValueType = avtBool) and CanEdit(column, dataRow) then begin
        PrepareDataRowCount(dataRow);
        v := column.DataColumn.DisplayItems[dataRow].AsBool;
        column.DataColumn.DisplayItems[dataRow].AsBool := not v;
        InvalidateCell(ACol, ARow);
        Self.DoValidate(column.DataColumn, dataRow);
      end;
    end;

    procedure TAVGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    begin
      inherited MouseDown(Button, Shift, X, Y);

      if not EditorMode and (fMouseTrackInfo.CurrCell.X >= StatusColNum) and
        (fMouseTrackInfo.CurrCell.Y >= 0)
      then fMouseTrackInfo.DoMouseDown(X, Y);
    end;

    procedure TAVGrid.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    begin
      inherited MouseUp(Button, Shift, X, Y);

      if not EditorMode and (fMouseTrackInfo.CurrCell.X >= StatusColNum) and
        (fMouseTrackInfo.CurrCell.Y >= 0)
      then fMouseTrackInfo.DoMouseUp(Button, Shift, X, Y);
    end;

    procedure TAVGrid.WmNCCalcSize(var Msg: TWMNCCalcSize);
    begin
      if GroupPannelShown then Inc(Msg.CalcSize_Params^.rgrc[0].Top, GridGroupPannelHeight);
      inherited;
    end;

    procedure TAVGrid.CalcDrawColors(ACol, ARow: Longint; AState: TGridDrawState);
    var column: TAVGridColumn;
        LColor: TColor;
        LStyle: TCustomStyleServices;
        LGridTheme: TThemedGrid;
    begin
      column := GetColumn(ACol);
      LStyle := StyleServices;

      if Assigned(column) then begin
        DrawColors.BackColor := column.Color;
        DrawColors.BaseBackColor := DrawColors.BackColor;
        if ARow = 0
          then DrawColors.TextColor := column.CaptionFont.Color
          else DrawColors.TextColor := column.Font.Color;
      end else begin
        DrawColors.BackColor := Canvas.Brush.Color;
        DrawColors.TextColor := Canvas.Font.Color;
      end;
      DrawColors.SelColor := fSelectionColor;

      if UseThemeForCell and (FInternalDrawingStyle = gdsThemed) then
      begin
        if (seClient in StyleElements) and
          (LStyle.GetElementColor(LStyle.GetElementDetails(tgCellNormal), ecFillColor, LColor)) and
          (LColor <> clNone) then
        begin
          DrawColors.BackColor := LColor;
          DrawColors.BaseBackColor := DrawColors.BackColor;
          if Assigned(column) and not IsWhite(column.Color) then
            DrawColors.BackColor := MergeColor(column.Color, DrawColors.BackColor, 50);
        end;

        if LStyle.GetElementColor(LStyle.GetElementDetails(tgGradientCellSelected), ecGradientColor1, LColor) and
           (LColor <> clNone) then
          DrawColors.SelColor := LColor;

        if (ACol = 0) or (ARow = 0) then
           DrawColors.TextColor := Canvas.Font.Color;
      end else
      if UseThemeForCell and (FInternalDrawingStyle = gdsGradient) then
      begin
        if LStyle.Enabled then begin
          if LStyle.GetElementColor(LStyle.GetElementDetails(tgGradientCellNormal),
           ecFillColor, LColor) and (LColor <> clNone) then
          begin
            DrawColors.BackColor := LColor;
            DrawColors.BaseBackColor := DrawColors.BackColor;
            if Assigned(column) and not IsWhite(column.Color) then
              DrawColors.BackColor := MergeColor(column.Color, DrawColors.BackColor, 50);
          end;

          if LStyle.GetElementColor(LStyle.GetElementDetails(tgGradientCellSelected), ecGradientColor1, LColor) and
           (LColor <> clNone) then
          DrawColors.SelColor := LColor;

          if (ACol = 0) or (ARow = 0) then
            DrawColors.TextColor := Canvas.Font.Color;
        end;
      end;

      DrawColors.CurrColor := DrawColors.BackColor;
    end;

    procedure TAVGrid.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
    var cellType: TAVCellType;
    begin
      if ShowStatusCol and (ACol = 0) then begin
        if ARow > 0 then cellType := ctStatus else cellType := ctCorner;
      end else
      if ARow = 0 then cellType := ctHeader else cellType := ctData;

      DrawText(ACol, ARow, ARect, AState, cellType);

      DrawSelectionEdge(ACol, ARow, ARect, AState);
    end;

    procedure TAVGrid.DrawSelectionEdge(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
    var leftShift, rightShift, bottomShift: integer;
    begin
      if (gdSelected in AState) then begin
        if (ColorLight(DrawColors.BaseBackColor) > 50) then
          Canvas.Pen.Color := DarkColor(DrawColors.SelColor, 60)
        else
          Canvas.Pen.Color := BrightColor(DrawColors.SelColor, 60);

        Canvas.Pen.Width := 1;
        leftShift := 0;
        rightShift := 0;
        if ARow = Selection.Bottom then bottomShift := 1 else bottomShift := 0;
        
        if ACol = Selection.Left then begin
          Canvas.MoveTo(ARect.Left, ARect.Top);
          if ARow = Selection.Bottom
            then Canvas.LineTo(ARect.Left, ARect.Bottom)
            else Canvas.LineTo(ARect.Left, ARect.Bottom + 1);
          leftShift := 0;
        end;
        if ACol = Selection.Right then begin
          Canvas.MoveTo(ARect.Right - 1, ARect.Top);
          if ARow = Selection.Bottom
            then Canvas.LineTo(ARect.Right - 1, ARect.Bottom)
            else Canvas.LineTo(ARect.Right - 1, ARect.Bottom + 1);
          rightShift := 1;
        end;
        if ARow = Selection.Top then begin
          Canvas.MoveTo(ARect.Left, ARect.Top);
          if ACol = Selection.Right
            then Canvas.LineTo(ARect.Right, ARect.Top)
            else Canvas.LineTo(ARect.Right + 1, ARect.Top);
        end;
        if ARow = Selection.Bottom then begin
          if ACol = Selection.Right
            then Canvas.MoveTo(ARect.Right - 1, ARect.Bottom - 1)
            else Canvas.MoveTo(ARect.Right, ARect.Bottom - 1);
          Canvas.LineTo(ARect.Left - 1, ARect.Bottom - 1);
        end;
      end;
    end;

    procedure TAVGrid.DrawCellBackground(const ARect: TRect; AColor: TColor; AState: TGridDrawState; ACol, ARow: Integer);
    begin
      if (ARow = 0) or ((ACol = 0) and Self.fShowStatusCol) then begin
        inherited DrawCellBackground(ARect, AColor, AState, ACol, ARow);
        CalcDrawColors(ACol, ARow, AState);
      end else begin
        CalcDrawColors(ACol, ARow, AState);
        DrawBackground(ARect, AState, ACol, ARow);
      end;
    end;

    procedure TAVGrid.DrawCellHighlight(const ARect: TRect; AState: TGridDrawState; ACol, ARow: Integer);
    begin
      CalcDrawColors(ACol, ARow, AState);
      DrawBackground(ARect, AState, ACol, ARow);
    end;

    procedure TAVGrid.DrawBackground(const ARect: TRect; AState: TGridDrawState; ACol, ARow: Integer);
    begin
      DrawColors.CurrColor := DrawColors.BackColor;
      if gdSelected in AState then
        DrawColors.CurrColor := MergeColor(DrawColors.CurrColor, DrawColors.SelColor, 50)
      else
      if (ARow >= Selection.Top) and (ARow <= Selection.Bottom) then
        DrawColors.CurrColor := MergeColor(DrawColors.CurrColor, DrawColors.SelColor, 40);

      if gdFocused in AState then begin
        if ColorLight(DrawColors.CurrColor) > 50
          then DrawColors.CurrColor := DarkColor(DrawColors.CurrColor, 20)
          else DrawColors.CurrColor := BrightColor(DrawColors.CurrColor, 20);
      end;
      DrawColors.TextColor := MakeColorMinDif(DrawColors.TextColor, DrawColors.CurrColor, 35);

      Canvas.Brush.Color := DrawColors.CurrColor;
      Canvas.FillRect(ARect);
    end;

    procedure TAVGrid.VisibleColumnsChanged;
    begin
      ColCount := Math.Max(1, Columns.VisibleColumns.Count) + StatusColNum;
      Invalidate;
    end;

    function TAVGrid.GetColumn(ACol: integer): TAVGridColumn;
    var colIndex: integer;
    begin
      colIndex := ACol - StatusColNum;
      if (colIndex >= 0) and (colIndex < fColumns.VisibleColumns.Count)
        then Result := fColumns.VisibleColumns[colIndex]
        else Result := nil;
    end;

    function TAVGrid.GetDataRow(ARow: integer): integer;
    begin
      Result := ARow - 1;
    end;

    function TAVGrid.GetRowFromDataRow(dataRow: integer): integer;
    begin
      Result := dataRow + 1;
    end;

    function TAVGrid.DataRowIsEmpty(dataRow: integer): boolean;
    var i: integer;
    begin
      Result := true;
      for i := 0 to fColumns.Count - 1 do begin
        if not fColumns[i].DataIsEmptry(dataRow) then begin
          Result := false;
          break;
        end;
      end;
    end;

    function TAVGrid.ValidateNewRow: boolean;
    var dataRow: integer;
    begin
      Result := true;
      if fGridState <> gstInNewRow then exit;
      dataRow := GetDataRow(RowCount - 1);
      if DataRowIsEmpty(dataRow) then begin
        Result := false;
      end;
      if Result then fGridState := gstNormal;
    end;

    function TAVGrid.ExitFromInNewRow: boolean;
    var dataRow: integer;
    begin
      Result := false;
      if fGridState <> gstInNewRow then exit;
      dataRow := GetDataRow(RowCount - 1);
      if not ValidateNewRow then begin
        if (Row = RowCount - 1) and (Row > 1) then Row := Row - 1;
        RowCount := Math.Max(2, RowCount - 1);
        RemoveDataRow(dataRow);
        Result := true;
      end;
      fGridState := gstNormal;
    end;

    procedure TAVGrid.RemoveDataRow(dataRow: integer);
    begin
      if (dataRow >= 0) and (dataRow < fData.DisplayRowCount) then begin
        fData.DeleteDisplayRow(dataRow);
      end;
    end;

    procedure TAVGrid.SetSelectionColor(value: TColor);
    begin
      if fSelectionColor <> value then begin
        fSelectionColor := value;
        Invalidate;
      end;
    end;

    procedure TAVGrid.SetEnableGrouping(value: boolean);
    begin
      if fEnableGrouping <> value then begin
        fEnableGrouping := value;
        if fEnableGrouping and AlwaysShowGroupPannel then
          ShowGroupPannel(true);
      end;
    end;

    procedure TAVGrid.SetAlwaysShowGroupPannel(value: boolean);
    begin
      if value <> fAlwaysShowGroupPannel then begin
        fAlwaysShowGroupPannel := value;
        ShowGroupPannel(fAlwaysShowGroupPannel);
      end;
    end;

    procedure TAVGrid.UpdateGridWindow;
    begin
      SetWindowPos(Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER or SWP_NOSENDCHANGING);
    end;

    function TAVGrid.GroupPannelShown: boolean;
    begin
      Result := Assigned(fGroupPannel) and fGroupPannel.Visible;
    end;

    function TAVGrid.GetBorderWidth: integer;
    var HasBorder, HasClientEdge: boolean;
    begin
      HasBorder := (GetWindowLong(Handle, GWL_STYLE) and WS_BORDER = WS_BORDER) or
            (GetWindowLong(Handle, GWL_EXSTYLE) and WS_EX_CLIENTEDGE = WS_EX_CLIENTEDGE);
      HasClientEdge := GetWindowLong(Handle, GWL_EXSTYLE) and WS_EX_CLIENTEDGE = WS_EX_CLIENTEDGE;
      Result := 0;
      if HasBorder then begin
        Inc(Result);
        if HasClientEdge then Inc(Result);
      end;
    end;

    procedure TAVGrid.SetGroupPannelPos;
    var border: integer;
        ord,pord: integer;
    begin
       if not Assigned(fGroupPannel) then exit;
       border := GetBorderWidth;
       fGroupPannel.Left := Left + border;
       fGroupPannel.Top := Top + border;
       fGroupPannel.Width := Width - 2*border;
       fGroupPannel.Height := GridGroupPannelHeight;

       if not Assigned(Parent) or not (Parent is TWinControl) then exit;

       ord := TPAWinControl(Parent).GetChildOrder(Self);
       pord := TPAWinControl(Parent).GetChildOrder(fGroupPannel);
       if pord <> (ord + 1) then
       begin
         TPAWinControl(Parent).SetChildOrder(fGroupPannel, ord);
         TPAWinControl(Parent).SetChildOrder(fGroupPannel, ord + 1);
       end;
    end;

    procedure TAVGrid.ShowGroupPannel(show: boolean);
    begin
      if not show and GroupPannelShown and not AlwaysShowGroupPannel then begin
        fGroupPannel.Visible := false;
        UpdateGridWindow;
      end;

      if show and EnableGrouping and not GroupPannelShown and not(csDesigning in ComponentState) and Assigned(Parent) then
      begin
        if not Assigned(fGroupPannel) then begin
          fGroupPannel := TAVGridGroupPannel.Create(Self);
          fGroupPannel.Parent := Parent;
        end;
        SetGroupPannelPos;
        fGroupPannel.Visible := true;
        UpdateGridWindow;
      end;
    end;

    procedure TAVGrid.Resize;
    begin
      inherited;
      SetGroupPannelPos;
    end;

    function TAVGrid.GetEditText(ACol, ARow: Longint): string;
    var column: TAVGridColumn;
    begin
      column := GetColumn(ACol);
      if (column <> nil) then begin
        Result := column.GetText(GetDataRow(ARow));
      end else
      begin
        Result := '';
      end;
    end;

    procedure TAVGrid.SetEditText(ACol, ARow: Longint; const Value: string);
    var column: TAVGridColumn;
        dataIndex: integer;
    begin
      column := GetColumn(ACol);
      if (column <> nil) then begin
        dataIndex := GetDataRow(ARow);
        PrepareDataRowCount(dataIndex);
        column.SetText(dataIndex, value);
        ExitFromInNewRow;
        InvalidateRow(ARow);
      end;
    end;

    procedure TAVGrid.PrepareDataRowCount(rowIndex: integer);
    begin
      if fData.DisplayRowCount <= rowIndex then fData.DisplayRowCount := rowIndex + 1;
    end;

    procedure TAVGrid.DoValidate(DataColumn: TAVBaseColumn; DataRow: integer);
    begin
      if Assigned(fOnValidate) then fOnValidate(Self, DataColumn, DataRow);
    end;

    procedure TAVGrid.KeyDown(var Key: Word; Shift: TShiftState);
    var inLastRow: boolean;
        column: TAVGridColumn;
    begin
      inLastRow := Row = RowCount - 1;
      inherited KeyDown(Key, Shift);
      HideEdit;
      if Shift = [] then
        case Key of
          VK_DOWN: begin
            if (fAcceptAddRow) and inLastRow then begin
              if (fGridState <> gstInNewRow) or ValidateNewRow then begin
                RowCount := RowCount + 1;
                Row := RowCount - 1;
                fGridState := gstInNewRow;
                InvalidateRow(Row);
              end;
            end;
          end;
          VK_UP: begin

          end;
        end;

      if not ReadOnly then begin
        column := GetColumn(Col);
        if (column <> nil) and not column.ReadOnly
        then ProcessEditKey(column, Key, Shift);
      end;
    end;

    procedure TAVGrid.ProcessEditKey(Column: TAVGridColumn; Key: Word; Shift: TShiftState);
    var v: boolean;
        i, dRow: integer;
    begin
      if ((Key = VK_SPACE) or (Key = VK_RETURN)) and
         (Column.ValueType = avtBool) and (Column.DataColumn <> nil) and
         (Column.DrawImage) and (Selection.Left = Selection.Right)
      then begin
        for i := Selection.Top to Selection.Bottom do begin
          dRow := GetDataRow(i);
          if CanEdit(Column, dRow) then begin
            PrepareDataRowCount(dRow);
            v := Column.DataColumn.DisplayItems[dRow].AsBool;
            Column.DataColumn.DisplayItems[dRow].AsBool := not v;
            InvalidateCell(Col, i);
            DoValidate(Column.DataColumn, dRow);
          end;
        end;
      end;
    end;

    procedure TAVGrid.CellSelected(ACol, ARow: Longint);
    begin
      if (fGridState = gstInNewRow) and (ARow <> RowCount - 1) then begin
        ExitFromInNewRow;
      end;
    end;

    procedure TAVGrid.SelectionMoved(const OldSel: TGridRect);
    var i: integer;
        iFrom, iTo: integer;
    begin
      inherited;
      if not HandleAllocated then Exit;

      if (OldSel.Left <> Selection.Left) or (OldSel.Right <> Selection.Right) then begin
        iFrom := Math.Min(OldSel.Top, Selection.Top);
        iTo := Math.Max(OldSel.Bottom, Selection.Bottom);
      end else begin
        if OldSel.Top = Selection.Top then begin
          iFrom := Math.Min(Selection.Bottom, OldSel.Bottom);
          iTo := Math.Max(Selection.Bottom, OldSel.Bottom);
        end else
        if OldSel.Bottom = Selection.Bottom then begin
          iFrom := Math.Min(Selection.Top, OldSel.Top);
          iTo := Math.Max(Selection.Top, OldSel.Top);
        end else begin
          iFrom := Math.Min(OldSel.Top, Selection.Top);
          iTo := Math.Max(OldSel.Bottom, Selection.Bottom);
        end;
      end;
      for i := iFrom to iTo do InvalidateRow(i);
    end;

    procedure TAVGrid.ColumnMoved(FromIndex, ToIndex: Longint);
    begin
      fColumns.MoveVisible(FromIndex - StatusColNum, ToIndex - StatusColNum);
      Invalidate;
    end;

    procedure TAVGrid.CallUpdateScroll;
    var msg: TMessage;
    begin
      msg.Msg := WM_VSCROLL;
      DoHandleStyleMessage(msg);
    end;

    procedure TAVGrid.Sort(SortColumns: TArray<TAVBaseColumn>);
    var dataRow, bookmark, restoreRow: integer;
    begin
      if fData.DisplayRowCount = 0 then exit;
      if Assigned(fOnBeforeSort) then fOnBeforeSort(Self, SortColumns);
      try
        dataRow := Self.GetDataRow(Row);
        bookmark := fData.GetBookmark(dataRow);
        fData.Sort(SortColumns);
        dataRow := fData.FindRowByBookmark(bookmark);
        if (dataRow >= 0) then begin
          restoreRow := GetRowFromDataRow(dataRow);
          if Row <> restoreRow then Row := restoreRow;
        end;
      finally
        if Assigned(fOnAfterSort) then fOnAfterSort(Self, SortColumns);
        Invalidate;
        CallUpdateScroll;
      end;
    end;

    procedure TAVGrid.FixedCellClick(ACol, ARow: Longint);
    var col: TAVGridColumn;
        arr: TArray<TAVBaseColumn>;
    begin
      if (ACol >= Self.StatusColNum) and (ARow = 0) then begin
        col := GetColumn(ACol);
        if col.DataColumn <> nil then begin
          SetLength(arr, 1);
          arr[0] := col.DataColumn;
          Sort(arr);
        end;
      end;

      inherited FixedCellClick(ACol, ARow);
    end;

end.
